// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/builtins/builtins-async-gen.h"
#include "src/builtins/builtins-utils-gen.h"
#include "src/builtins/builtins.h"
#include "src/code-factory.h"
#include "src/code-stub-assembler.h"
#include "src/frames-inl.h"
#include "src/objects/js-generator.h"
#include "src/objects/js-promise.h"

namespace v8 {
namespace internal {

    using compiler::Node;

    namespace {

        class AsyncGeneratorBuiltinsAssembler : public AsyncBuiltinsAssembler {
        public:
            explicit AsyncGeneratorBuiltinsAssembler(CodeAssemblerState* state)
                : AsyncBuiltinsAssembler(state)
            {
            }

            inline Node* TaggedIsAsyncGenerator(Node* tagged_object)
            {
                TNode<BoolT> if_notsmi = TaggedIsNotSmi(tagged_object);
                return Select<BoolT>(
                    if_notsmi,
                    [=] {
                        return HasInstanceType(
                            tagged_object, JS_ASYNC_GENERATOR_OBJECT_TYPE);
                    },
                    [=] { return if_notsmi; });
            }
            inline Node* LoadGeneratorState(Node* const generator)
            {
                return LoadObjectField(generator, JSGeneratorObject::kContinuationOffset);
            }

            inline TNode<BoolT> IsGeneratorStateClosed(SloppyTNode<Smi> const state)
            {
                return SmiEqual(state, SmiConstant(JSGeneratorObject::kGeneratorClosed));
            }
            inline TNode<BoolT> IsGeneratorClosed(Node* const generator)
            {
                return IsGeneratorStateClosed(LoadGeneratorState(generator));
            }

            inline TNode<BoolT> IsGeneratorStateSuspended(SloppyTNode<Smi> const state)
            {
                return SmiGreaterThanOrEqual(state, SmiConstant(0));
            }

            inline TNode<BoolT> IsGeneratorSuspended(Node* const generator)
            {
                return IsGeneratorStateSuspended(LoadGeneratorState(generator));
            }

            inline TNode<BoolT> IsGeneratorStateSuspendedAtStart(
                SloppyTNode<Smi> const state)
            {
                return SmiEqual(state, SmiConstant(0));
            }

            inline TNode<BoolT> IsGeneratorStateNotExecuting(
                SloppyTNode<Smi> const state)
            {
                return SmiNotEqual(state,
                    SmiConstant(JSGeneratorObject::kGeneratorExecuting));
            }
            inline TNode<BoolT> IsGeneratorNotExecuting(Node* const generator)
            {
                return IsGeneratorStateNotExecuting(LoadGeneratorState(generator));
            }

            inline TNode<BoolT> IsGeneratorAwaiting(Node* const generator)
            {
                TNode<Object> is_generator_awaiting = LoadObjectField(generator, JSAsyncGeneratorObject::kIsAwaitingOffset);
                return WordEqual(is_generator_awaiting, SmiConstant(1));
            }

            inline void SetGeneratorAwaiting(Node* const generator)
            {
                CSA_ASSERT(this, Word32BinaryNot(IsGeneratorAwaiting(generator)));
                StoreObjectFieldNoWriteBarrier(
                    generator, JSAsyncGeneratorObject::kIsAwaitingOffset, SmiConstant(1));
                CSA_ASSERT(this, IsGeneratorAwaiting(generator));
            }

            inline void SetGeneratorNotAwaiting(Node* const generator)
            {
                CSA_ASSERT(this, IsGeneratorAwaiting(generator));
                StoreObjectFieldNoWriteBarrier(
                    generator, JSAsyncGeneratorObject::kIsAwaitingOffset, SmiConstant(0));
                CSA_ASSERT(this, Word32BinaryNot(IsGeneratorAwaiting(generator)));
            }

            inline void CloseGenerator(Node* const generator)
            {
                StoreObjectFieldNoWriteBarrier(
                    generator, JSGeneratorObject::kContinuationOffset,
                    SmiConstant(JSGeneratorObject::kGeneratorClosed));
            }

            inline Node* IsFastJSIterResult(Node* const value, Node* const context)
            {
                CSA_ASSERT(this, TaggedIsNotSmi(value));
                Node* const native_context = LoadNativeContext(context);
                return WordEqual(
                    LoadMap(value),
                    LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX));
            }

            inline Node* LoadFirstAsyncGeneratorRequestFromQueue(Node* const generator)
            {
                return LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset);
            }

            inline Node* LoadResumeTypeFromAsyncGeneratorRequest(Node* const request)
            {
                return LoadObjectField(request, AsyncGeneratorRequest::kResumeModeOffset);
            }

            inline Node* LoadPromiseFromAsyncGeneratorRequest(Node* const request)
            {
                return LoadObjectField(request, AsyncGeneratorRequest::kPromiseOffset);
            }

            inline Node* LoadValueFromAsyncGeneratorRequest(Node* const request)
            {
                return LoadObjectField(request, AsyncGeneratorRequest::kValueOffset);
            }

            inline TNode<BoolT> IsAbruptResumeType(SloppyTNode<Smi> const resume_type)
            {
                return SmiNotEqual(resume_type, SmiConstant(JSGeneratorObject::kNext));
            }

            void AsyncGeneratorEnqueue(CodeStubArguments* args, Node* context,
                Node* generator, Node* value,
                JSAsyncGeneratorObject::ResumeMode resume_mode,
                const char* method_name);

            Node* TakeFirstAsyncGeneratorRequestFromQueue(Node* generator);
            Node* TakeFirstAsyncGeneratorRequestFromQueueIfPresent(Node* generator,
                Label* if_not_present);
            void AddAsyncGeneratorRequestToQueue(Node* generator, Node* request);

            Node* AllocateAsyncGeneratorRequest(
                JSAsyncGeneratorObject::ResumeMode resume_mode, Node* resume_value,
                Node* promise);

            // Shared implementation of the catchable and uncatchable variations of Await
            // for AsyncGenerators.
            template <typename Descriptor>
            void AsyncGeneratorAwait(bool is_catchable);
            void AsyncGeneratorAwaitResumeClosure(
                Node* context, Node* value,
                JSAsyncGeneratorObject::ResumeMode resume_mode);
        };

        // Shared implementation for the 3 Async Iterator protocol methods of Async
        // Generators.
        void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorEnqueue(
            CodeStubArguments* args, Node* context, Node* generator, Node* value,
            JSAsyncGeneratorObject::ResumeMode resume_mode, const char* method_name)
        {
            // AsyncGeneratorEnqueue produces a new Promise, and appends it to the list
            // of async generator requests to be executed. If the generator is not
            // presently executing, then this method will loop through, processing each
            // request from front to back.
            // This loop resides in AsyncGeneratorResumeNext.
            Node* promise = AllocateAndInitJSPromise(context);

            Label enqueue(this), if_receiverisincompatible(this, Label::kDeferred);

            Branch(TaggedIsAsyncGenerator(generator), &enqueue,
                &if_receiverisincompatible);

            BIND(&enqueue);
            {
                Label done(this);
                Node* const req = AllocateAsyncGeneratorRequest(resume_mode, value, promise);

                AddAsyncGeneratorRequestToQueue(generator, req);

                // Let state be generator.[[AsyncGeneratorState]]
                // If state is not "executing", then
                //     Perform AsyncGeneratorResumeNext(Generator)
                // Check if the {receiver} is running or already closed.
                TNode<Smi> continuation = CAST(LoadGeneratorState(generator));

                GotoIf(SmiEqual(continuation,
                           SmiConstant(JSAsyncGeneratorObject::kGeneratorExecuting)),
                    &done);

                CallBuiltin(Builtins::kAsyncGeneratorResumeNext, context, generator);

                Goto(&done);
                BIND(&done);
                args->PopAndReturn(promise);
            }

            BIND(&if_receiverisincompatible);
            {
                Node* const error = MakeTypeError(MessageTemplate::kIncompatibleMethodReceiver, context,
                    StringConstant(method_name), generator);

                CallBuiltin(Builtins::kRejectPromise, context, promise, error,
                    TrueConstant());
                args->PopAndReturn(promise);
            }
        }

        Node* AsyncGeneratorBuiltinsAssembler::AllocateAsyncGeneratorRequest(
            JSAsyncGeneratorObject::ResumeMode resume_mode, Node* resume_value,
            Node* promise)
        {
            CSA_SLOW_ASSERT(this, HasInstanceType(promise, JS_PROMISE_TYPE));
            Node* request = Allocate(AsyncGeneratorRequest::kSize);
            StoreMapNoWriteBarrier(request, RootIndex::kAsyncGeneratorRequestMap);
            StoreObjectFieldNoWriteBarrier(request, AsyncGeneratorRequest::kNextOffset,
                UndefinedConstant());
            StoreObjectFieldNoWriteBarrier(request,
                AsyncGeneratorRequest::kResumeModeOffset,
                SmiConstant(resume_mode));
            StoreObjectFieldNoWriteBarrier(request, AsyncGeneratorRequest::kValueOffset,
                resume_value);
            StoreObjectFieldNoWriteBarrier(request, AsyncGeneratorRequest::kPromiseOffset,
                promise);
            StoreObjectFieldRoot(request, AsyncGeneratorRequest::kNextOffset,
                RootIndex::kUndefinedValue);
            return request;
        }

        void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorAwaitResumeClosure(
            Node* context, Node* value,
            JSAsyncGeneratorObject::ResumeMode resume_mode)
        {
            Node* const generator = LoadContextElement(context, Context::EXTENSION_INDEX);
            CSA_SLOW_ASSERT(this, TaggedIsAsyncGenerator(generator));

            SetGeneratorNotAwaiting(generator);

            CSA_SLOW_ASSERT(this, IsGeneratorSuspended(generator));

            // Remember the {resume_mode} for the {generator}.
            StoreObjectFieldNoWriteBarrier(generator,
                JSGeneratorObject::kResumeModeOffset,
                SmiConstant(resume_mode));

            CallStub(CodeFactory::ResumeGenerator(isolate()), context, value, generator);

            TailCallBuiltin(Builtins::kAsyncGeneratorResumeNext, context, generator);
        }

        template <typename Descriptor>
        void AsyncGeneratorBuiltinsAssembler::AsyncGeneratorAwait(bool is_catchable)
        {
            TNode<JSAsyncGeneratorObject> async_generator_object = CAST(Parameter(Descriptor::kAsyncGeneratorObject));
            TNode<Object> value = CAST(Parameter(Descriptor::kValue));
            TNode<Context> context = CAST(Parameter(Descriptor::kContext));

            TNode<AsyncGeneratorRequest> request = CAST(LoadFirstAsyncGeneratorRequestFromQueue(async_generator_object));
            TNode<JSPromise> outer_promise = LoadObjectField<JSPromise>(
                request, AsyncGeneratorRequest::kPromiseOffset);

            const int resolve_index = Context::ASYNC_GENERATOR_AWAIT_RESOLVE_SHARED_FUN;
            const int reject_index = Context::ASYNC_GENERATOR_AWAIT_REJECT_SHARED_FUN;

            SetGeneratorAwaiting(async_generator_object);
            Await(context, async_generator_object, value, outer_promise, resolve_index,
                reject_index, is_catchable);
            Return(UndefinedConstant());
        }

        void AsyncGeneratorBuiltinsAssembler::AddAsyncGeneratorRequestToQueue(
            Node* generator, Node* request)
        {
            VARIABLE(var_current, MachineRepresentation::kTagged);
            Label empty(this), loop(this, &var_current), done(this);

            var_current.Bind(
                LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset));
            Branch(IsUndefined(var_current.value()), &empty, &loop);

            BIND(&empty);
            {
                StoreObjectField(generator, JSAsyncGeneratorObject::kQueueOffset, request);
                Goto(&done);
            }

            BIND(&loop);
            {
                Label loop_next(this), next_empty(this);
                Node* current = var_current.value();
                Node* next = LoadObjectField(current, AsyncGeneratorRequest::kNextOffset);

                Branch(IsUndefined(next), &next_empty, &loop_next);
                BIND(&next_empty);
                {
                    StoreObjectField(current, AsyncGeneratorRequest::kNextOffset, request);
                    Goto(&done);
                }

                BIND(&loop_next);
                {
                    var_current.Bind(next);
                    Goto(&loop);
                }
            }
            BIND(&done);
        }

        Node* AsyncGeneratorBuiltinsAssembler::TakeFirstAsyncGeneratorRequestFromQueue(
            Node* generator)
        {
            // Removes and returns the first AsyncGeneratorRequest from a
            // JSAsyncGeneratorObject's queue. Asserts that the queue is not empty.
            CSA_ASSERT(this, TaggedIsAsyncGenerator(generator));
            Node* request = LoadObjectField(generator, JSAsyncGeneratorObject::kQueueOffset);
            CSA_ASSERT(this, IsNotUndefined(request));

            Node* next = LoadObjectField(request, AsyncGeneratorRequest::kNextOffset);

            StoreObjectField(generator, JSAsyncGeneratorObject::kQueueOffset, next);
            return request;
        }
    } // namespace

    // https://tc39.github.io/proposal-async-iteration/
    // Section #sec-asyncgenerator-prototype-next
    TF_BUILTIN(AsyncGeneratorPrototypeNext, AsyncGeneratorBuiltinsAssembler)
    {
        const int kValueArg = 0;

        Node* argc = ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount));
        CodeStubArguments args(this, argc);

        Node* generator = args.GetReceiver();
        Node* value = args.GetOptionalArgumentValue(kValueArg);
        Node* context = Parameter(Descriptor::kContext);

        AsyncGeneratorEnqueue(&args, context, generator, value,
            JSAsyncGeneratorObject::kNext,
            "[AsyncGenerator].prototype.next");
    }

    // https://tc39.github.io/proposal-async-iteration/
    // Section #sec-asyncgenerator-prototype-return
    TF_BUILTIN(AsyncGeneratorPrototypeReturn, AsyncGeneratorBuiltinsAssembler)
    {
        const int kValueArg = 0;

        Node* argc = ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount));
        CodeStubArguments args(this, argc);

        Node* generator = args.GetReceiver();
        Node* value = args.GetOptionalArgumentValue(kValueArg);
        Node* context = Parameter(Descriptor::kContext);

        AsyncGeneratorEnqueue(&args, context, generator, value,
            JSAsyncGeneratorObject::kReturn,
            "[AsyncGenerator].prototype.return");
    }

    // https://tc39.github.io/proposal-async-iteration/
    // Section #sec-asyncgenerator-prototype-throw
    TF_BUILTIN(AsyncGeneratorPrototypeThrow, AsyncGeneratorBuiltinsAssembler)
    {
        const int kValueArg = 0;

        Node* argc = ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount));
        CodeStubArguments args(this, argc);

        Node* generator = args.GetReceiver();
        Node* value = args.GetOptionalArgumentValue(kValueArg);
        Node* context = Parameter(Descriptor::kContext);

        AsyncGeneratorEnqueue(&args, context, generator, value,
            JSAsyncGeneratorObject::kThrow,
            "[AsyncGenerator].prototype.throw");
    }

    TF_BUILTIN(AsyncGeneratorAwaitResolveClosure, AsyncGeneratorBuiltinsAssembler)
    {
        Node* value = Parameter(Descriptor::kValue);
        Node* context = Parameter(Descriptor::kContext);
        AsyncGeneratorAwaitResumeClosure(context, value,
            JSAsyncGeneratorObject::kNext);
    }

    TF_BUILTIN(AsyncGeneratorAwaitRejectClosure, AsyncGeneratorBuiltinsAssembler)
    {
        Node* value = Parameter(Descriptor::kValue);
        Node* context = Parameter(Descriptor::kContext);
        AsyncGeneratorAwaitResumeClosure(context, value,
            JSAsyncGeneratorObject::kThrow);
    }

    TF_BUILTIN(AsyncGeneratorAwaitUncaught, AsyncGeneratorBuiltinsAssembler)
    {
        const bool kIsCatchable = false;
        AsyncGeneratorAwait<Descriptor>(kIsCatchable);
    }

    TF_BUILTIN(AsyncGeneratorAwaitCaught, AsyncGeneratorBuiltinsAssembler)
    {
        const bool kIsCatchable = true;
        AsyncGeneratorAwait<Descriptor>(kIsCatchable);
    }

    TF_BUILTIN(AsyncGeneratorResumeNext, AsyncGeneratorBuiltinsAssembler)
    {
        typedef AsyncGeneratorResumeNextDescriptor Descriptor;
        Node* const generator = Parameter(Descriptor::kGenerator);
        Node* const context = Parameter(Descriptor::kContext);

        // The penultimate step of proposal-async-iteration/#sec-asyncgeneratorresolve
        // and proposal-async-iteration/#sec-asyncgeneratorreject both recursively
        // invoke AsyncGeneratorResumeNext() again.
        //
        // This implementation does not implement this recursively, but instead
        // performs a loop in AsyncGeneratorResumeNext, which  continues as long as
        // there is an AsyncGeneratorRequest in the queue, and as long as the
        // generator is not suspended due to an AwaitExpression.
        VARIABLE(var_state, MachineRepresentation::kTaggedSigned,
            LoadGeneratorState(generator));
        VARIABLE(var_next, MachineRepresentation::kTagged,
            LoadFirstAsyncGeneratorRequestFromQueue(generator));
        Variable* loop_variables[] = { &var_state, &var_next };
        Label start(this, 2, loop_variables);
        Goto(&start);
        BIND(&start);

        CSA_ASSERT(this, IsGeneratorNotExecuting(generator));

        // Stop resuming if suspended for Await.
        ReturnIf(IsGeneratorAwaiting(generator), UndefinedConstant());

        // Stop resuming if request queue is empty.
        ReturnIf(IsUndefined(var_next.value()), UndefinedConstant());

        Node* const next = var_next.value();
        TNode<Smi> const resume_type = CAST(LoadResumeTypeFromAsyncGeneratorRequest(next));

        Label if_abrupt(this), if_normal(this), resume_generator(this);
        Branch(IsAbruptResumeType(resume_type), &if_abrupt, &if_normal);
        BIND(&if_abrupt);
        {
            Label settle_promise(this), if_return(this), if_throw(this);
            GotoIfNot(IsGeneratorStateSuspendedAtStart(var_state.value()),
                &settle_promise);
            CloseGenerator(generator);
            var_state.Bind(SmiConstant(JSGeneratorObject::kGeneratorClosed));
            Goto(&settle_promise);

            BIND(&settle_promise);
            Node* next_value = LoadValueFromAsyncGeneratorRequest(next);
            Branch(SmiEqual(resume_type, SmiConstant(JSGeneratorObject::kReturn)),
                &if_return, &if_throw);

            BIND(&if_return);
            // For "return" completions, await the sent value. If the Await succeeds,
            // and the generator is not closed, resume the generator with a "return"
            // completion to allow `finally` blocks to be evaluated. Otherwise, perform
            // AsyncGeneratorResolve(awaitedValue, true). If the await fails and the
            // generator is not closed, resume the generator with a "throw" completion.
            // If the generator was closed, perform AsyncGeneratorReject(thrownValue).
            // In all cases, the last step is to call AsyncGeneratorResumeNext.
            Node* is_caught = CallRuntime(Runtime::kAsyncGeneratorHasCatchHandlerForPC,
                context, generator);
            TailCallBuiltin(Builtins::kAsyncGeneratorReturn, context, generator,
                next_value, is_caught);

            BIND(&if_throw);
            GotoIfNot(IsGeneratorStateClosed(var_state.value()), &resume_generator);
            CallBuiltin(Builtins::kAsyncGeneratorReject, context, generator,
                next_value);
            var_next.Bind(LoadFirstAsyncGeneratorRequestFromQueue(generator));
            Goto(&start);
        }

        BIND(&if_normal);
        {
            GotoIfNot(IsGeneratorStateClosed(var_state.value()), &resume_generator);
            CallBuiltin(Builtins::kAsyncGeneratorResolve, context, generator,
                UndefinedConstant(), TrueConstant());
            var_state.Bind(LoadGeneratorState(generator));
            var_next.Bind(LoadFirstAsyncGeneratorRequestFromQueue(generator));
            Goto(&start);
        }

        BIND(&resume_generator);
        {
            // Remember the {resume_type} for the {generator}.
            StoreObjectFieldNoWriteBarrier(
                generator, JSGeneratorObject::kResumeModeOffset, resume_type);
            CallStub(CodeFactory::ResumeGenerator(isolate()), context,
                LoadValueFromAsyncGeneratorRequest(next), generator);
            var_state.Bind(LoadGeneratorState(generator));
            var_next.Bind(LoadFirstAsyncGeneratorRequestFromQueue(generator));
            Goto(&start);
        }
    }

    TF_BUILTIN(AsyncGeneratorResolve, AsyncGeneratorBuiltinsAssembler)
    {
        Node* const generator = Parameter(Descriptor::kGenerator);
        Node* const value = Parameter(Descriptor::kValue);
        Node* const done = Parameter(Descriptor::kDone);
        Node* const context = Parameter(Descriptor::kContext);

        CSA_SLOW_ASSERT(this, TaggedIsAsyncGenerator(generator));
        CSA_ASSERT(this, Word32BinaryNot(IsGeneratorAwaiting(generator)));

        // This operation should be called only when the `value` parameter has been
        // Await-ed. Typically, this means `value` is not a JSPromise value. However,
        // it may be a JSPromise value whose "then" method has been overridden to a
        // non-callable value. This can't be checked with assertions due to being
        // observable, but keep it in mind.

        Node* const next = TakeFirstAsyncGeneratorRequestFromQueue(generator);
        Node* const promise = LoadPromiseFromAsyncGeneratorRequest(next);

        // Let iteratorResult be CreateIterResultObject(value, done).
        Node* const iter_result = Allocate(JSIteratorResult::kSize);
        {
            Node* map = LoadContextElement(LoadNativeContext(context),
                Context::ITERATOR_RESULT_MAP_INDEX);
            StoreMapNoWriteBarrier(iter_result, map);
            StoreObjectFieldRoot(iter_result, JSIteratorResult::kPropertiesOrHashOffset,
                RootIndex::kEmptyFixedArray);
            StoreObjectFieldRoot(iter_result, JSIteratorResult::kElementsOffset,
                RootIndex::kEmptyFixedArray);
            StoreObjectFieldNoWriteBarrier(iter_result, JSIteratorResult::kValueOffset,
                value);
            StoreObjectFieldNoWriteBarrier(iter_result, JSIteratorResult::kDoneOffset,
                done);
        }

        // We know that {iter_result} itself doesn't have any "then" property (a
        // freshly allocated IterResultObject only has "value" and "done" properties)
        // and we also know that the [[Prototype]] of {iter_result} is the intrinsic
        // %ObjectPrototype%. So we can skip the [[Resolve]] logic here completely
        // and directly call into the FulfillPromise operation if we can prove
        // that the %ObjectPrototype% also doesn't have any "then" property. This
        // is guarded by the Promise#then() protector.
        // If the PromiseHooks are enabled, we cannot take the shortcut here, since
        // the "promiseResolve" hook would not be fired otherwise.
        Label if_fast(this), if_slow(this, Label::kDeferred), return_promise(this);
        GotoIfForceSlowPath(&if_slow);
        GotoIf(IsPromiseHookEnabled(), &if_slow);
        Branch(IsPromiseThenProtectorCellInvalid(), &if_slow, &if_fast);

        BIND(&if_fast);
        {
            // Skip the "then" on {iter_result} and directly fulfill the {promise}
            // with the {iter_result}.
            CallBuiltin(Builtins::kFulfillPromise, context, promise, iter_result);
            Goto(&return_promise);
        }

        BIND(&if_slow);
        {
            // Perform Call(promiseCapability.[[Resolve]], undefined, «iteratorResult»).
            CallBuiltin(Builtins::kResolvePromise, context, promise, iter_result);
            Goto(&return_promise);
        }

        // Per spec, AsyncGeneratorResolve() returns undefined. However, for the
        // benefit of %TraceExit(), return the Promise.
        BIND(&return_promise);
        Return(promise);
    }

    TF_BUILTIN(AsyncGeneratorReject, AsyncGeneratorBuiltinsAssembler)
    {
        typedef AsyncGeneratorRejectDescriptor Descriptor;
        Node* const generator = Parameter(Descriptor::kGenerator);
        Node* const value = Parameter(Descriptor::kValue);
        Node* const context = Parameter(Descriptor::kContext);

        Node* const next = TakeFirstAsyncGeneratorRequestFromQueue(generator);
        Node* const promise = LoadPromiseFromAsyncGeneratorRequest(next);

        Return(CallBuiltin(Builtins::kRejectPromise, context, promise, value,
            TrueConstant()));
    }

    TF_BUILTIN(AsyncGeneratorYield, AsyncGeneratorBuiltinsAssembler)
    {
        Node* const generator = Parameter(Descriptor::kGenerator);
        Node* const value = Parameter(Descriptor::kValue);
        Node* const is_caught = Parameter(Descriptor::kIsCaught);
        Node* const context = Parameter(Descriptor::kContext);

        Node* const request = LoadFirstAsyncGeneratorRequestFromQueue(generator);
        Node* const outer_promise = LoadPromiseFromAsyncGeneratorRequest(request);

        const int on_resolve = Context::ASYNC_GENERATOR_YIELD_RESOLVE_SHARED_FUN;
        const int on_reject = Context::ASYNC_GENERATOR_AWAIT_REJECT_SHARED_FUN;

        SetGeneratorAwaiting(generator);
        Await(context, generator, value, outer_promise, on_resolve, on_reject,
            is_caught);
        Return(UndefinedConstant());
    }

    TF_BUILTIN(AsyncGeneratorYieldResolveClosure, AsyncGeneratorBuiltinsAssembler)
    {
        Node* const context = Parameter(Descriptor::kContext);
        Node* const value = Parameter(Descriptor::kValue);
        Node* const generator = LoadContextElement(context, Context::EXTENSION_INDEX);

        SetGeneratorNotAwaiting(generator);

        // Per proposal-async-iteration/#sec-asyncgeneratoryield step 9
        // Return ! AsyncGeneratorResolve(_F_.[[Generator]], _value_, *false*).
        CallBuiltin(Builtins::kAsyncGeneratorResolve, context, generator, value,
            FalseConstant());

        TailCallBuiltin(Builtins::kAsyncGeneratorResumeNext, context, generator);
    }

    TF_BUILTIN(AsyncGeneratorReturn, AsyncGeneratorBuiltinsAssembler)
    {
        // AsyncGeneratorReturn is called when resuming requests with "return" resume
        // modes. It is similar to AsyncGeneratorAwait(), but selects different
        // resolve/reject closures depending on whether or not the generator is marked
        // as closed.
        //
        // In particular, non-closed generators will resume the generator with either
        // "return" or "throw" resume modes, allowing finally blocks or catch blocks
        // to be evaluated, as if the `await` were performed within the body of the
        // generator. (per proposal-async-iteration/#sec-asyncgeneratoryield step 8.b)
        //
        // Closed generators do not resume the generator in the resolve/reject
        // closures, but instead simply perform AsyncGeneratorResolve or
        // AsyncGeneratorReject with the awaited value
        // (per proposal-async-iteration/#sec-asyncgeneratorresumenext step 10.b.i)
        //
        // In all cases, the final step is to jump back to AsyncGeneratorResumeNext.
        Node* const generator = Parameter(Descriptor::kGenerator);
        Node* const value = Parameter(Descriptor::kValue);
        Node* const is_caught = Parameter(Descriptor::kIsCaught);
        Node* const req = LoadFirstAsyncGeneratorRequestFromQueue(generator);
        CSA_ASSERT(this, IsNotUndefined(req));

        Label perform_await(this);
        VARIABLE(var_on_resolve, MachineType::PointerRepresentation(),
            IntPtrConstant(
                Context::ASYNC_GENERATOR_RETURN_CLOSED_RESOLVE_SHARED_FUN));
        VARIABLE(
            var_on_reject, MachineType::PointerRepresentation(),
            IntPtrConstant(Context::ASYNC_GENERATOR_RETURN_CLOSED_REJECT_SHARED_FUN));

        Node* const state = LoadGeneratorState(generator);
        GotoIf(IsGeneratorStateClosed(state), &perform_await);
        var_on_resolve.Bind(
            IntPtrConstant(Context::ASYNC_GENERATOR_RETURN_RESOLVE_SHARED_FUN));
        var_on_reject.Bind(
            IntPtrConstant(Context::ASYNC_GENERATOR_AWAIT_REJECT_SHARED_FUN));
        Goto(&perform_await);

        BIND(&perform_await);

        SetGeneratorAwaiting(generator);
        Node* const context = Parameter(Descriptor::kContext);
        Node* const outer_promise = LoadPromiseFromAsyncGeneratorRequest(req);
        Await(context, generator, value, outer_promise, var_on_resolve.value(),
            var_on_reject.value(), is_caught);

        Return(UndefinedConstant());
    }

    // On-resolve closure for Await in AsyncGeneratorReturn
    // Resume the generator with "return" resume_mode, and finally perform
    // AsyncGeneratorResumeNext. Per
    // proposal-async-iteration/#sec-asyncgeneratoryield step 8.e
    TF_BUILTIN(AsyncGeneratorReturnResolveClosure,
        AsyncGeneratorBuiltinsAssembler)
    {
        Node* const context = Parameter(Descriptor::kContext);
        Node* const value = Parameter(Descriptor::kValue);
        AsyncGeneratorAwaitResumeClosure(context, value, JSGeneratorObject::kReturn);
    }

    // On-resolve closure for Await in AsyncGeneratorReturn
    // Perform AsyncGeneratorResolve({awaited_value}, true) and finally perform
    // AsyncGeneratorResumeNext.
    TF_BUILTIN(AsyncGeneratorReturnClosedResolveClosure,
        AsyncGeneratorBuiltinsAssembler)
    {
        Node* const context = Parameter(Descriptor::kContext);
        Node* const value = Parameter(Descriptor::kValue);
        Node* const generator = LoadContextElement(context, Context::EXTENSION_INDEX);

        SetGeneratorNotAwaiting(generator);

        // https://tc39.github.io/proposal-async-iteration/
        //    #async-generator-resume-next-return-processor-fulfilled step 2:
        //  Return ! AsyncGeneratorResolve(_F_.[[Generator]], _value_, *true*).
        CallBuiltin(Builtins::kAsyncGeneratorResolve, context, generator, value,
            TrueConstant());

        TailCallBuiltin(Builtins::kAsyncGeneratorResumeNext, context, generator);
    }

    TF_BUILTIN(AsyncGeneratorReturnClosedRejectClosure,
        AsyncGeneratorBuiltinsAssembler)
    {
        Node* const context = Parameter(Descriptor::kContext);
        Node* const value = Parameter(Descriptor::kValue);
        Node* const generator = LoadContextElement(context, Context::EXTENSION_INDEX);

        SetGeneratorNotAwaiting(generator);

        // https://tc39.github.io/proposal-async-iteration/
        //    #async-generator-resume-next-return-processor-rejected step 2:
        // Return ! AsyncGeneratorReject(_F_.[[Generator]], _reason_).
        CallBuiltin(Builtins::kAsyncGeneratorReject, context, generator, value);

        TailCallBuiltin(Builtins::kAsyncGeneratorResumeNext, context, generator);
    }

} // namespace internal
} // namespace v8
